[id].vue 76 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278
  1. // 商品详情页面
  2. <template>
  3. <div>
  4. <StoreHeaderCat ref="headercat" @updateFllow="updateFllow" />
  5. <div class="sld_goods_detail">
  6. <!-- 内容区顶部固定 start -->
  7. <div class="contain_con" v-if="containCon">
  8. <div class="contain_content flex_row_center_center">
  9. <div class="contain_con_left flex_row_start_center">
  10. <span
  11. class="store_type"
  12. v-if="goodsDetail.data.storeInf.isOwnStore == 1"
  13. >{{ L["自营"] }}</span
  14. >
  15. <span class="store_title">{{
  16. goodsDetail.data.storeInf.storeName
  17. }}</span>
  18. </div>
  19. <div class="contain_con_right">
  20. <div class="goods_description_title flex_row_between_center">
  21. <div class="description_title_left flex_row_start_center">
  22. <span
  23. class="cursor_pointer"
  24. :class="{ description_active: goodsDesctionType == 'detail' }"
  25. @click="goodsDescType('detail')"
  26. >{{ L["商品详情"] }}</span
  27. >
  28. <span
  29. class="cursor_pointer"
  30. :class="{
  31. description_active: goodsDesctionType == 'evaluate',
  32. }"
  33. @click="goodsDescType('evaluate')"
  34. >{{ L["商品说明"] }}</span
  35. >
  36. <span
  37. class="cursor_pointer"
  38. :class="{
  39. description_active: goodsDesctionType == 'service',
  40. }"
  41. @click="goodsDescType('service')"
  42. >{{ L["商品服务"] }}</span
  43. >
  44. <span
  45. class="cursor_pointer"
  46. :class="{
  47. description_active: goodsDesctionType == 'salestore',
  48. }"
  49. @click="goodsDescType('salestore')"
  50. >{{ L["店铺热销"] }}</span
  51. >
  52. </div>
  53. </div>
  54. </div>
  55. </div>
  56. </div>
  57. <!-- 内容区顶部固定 end -->
  58. <div class="goods_detail_content self_background" v-if="goodsDetail.data">
  59. <!-- 商品所属分类 ,联系客服,关注店铺 start-->
  60. <div class="goods_about_con">
  61. <div class="goods_about flex_row_between_center">
  62. <div class="goods_classify">
  63. <span
  64. v-for="(item, index) in goodsDetail.data.categoryPath"
  65. :key="index"
  66. >
  67. <nuxt-link
  68. :to="`/goods/list/${calcProductName(item)}_v-${ goodsDetail.data.categoryIdPath[index] }_gid-${index + 1}${index > 0? '_pid-' + goodsDetail.data.categoryIdPath[index - 1]: '_pid-0'
  69. }`"
  70. target="_blank"
  71. >
  72. {{ item }}</nuxt-link
  73. >
  74. <i> > </i>
  75. </span>
  76. <span style="color: #333333">{{
  77. goodsDetail.data.goodsName
  78. }}</span>
  79. </div>
  80. <div class="goods_about_right flex_row_between_center">
  81. <router-link
  82. target="_blank"
  83. :to="'/store/'+ calcProductName(goodsDetail.data.storeInf.storeName) +'_'+ goodsDetail.data.storeInf.storeId"
  84. class="goods_about_store flex_row_between_center"
  85. v-if="goodsDetail.data.storeInf"
  86. >
  87. <span>{{ goodsDetail.data.storeInf.storeName }}</span>
  88. <span
  89. v-if="
  90. goodsDetail.data.storeInf &&
  91. goodsDetail.data.storeInf.isOwnStore == '1'
  92. "
  93. >{{ L["自营"] }}</span
  94. >
  95. </router-link>
  96. <div
  97. class="contact_service focus_store"
  98. @click="focusStore"
  99. v-if="goodsDetail.data.storeInf"
  100. >
  101. <img
  102. src="/goods/collection.png"
  103. alt=""
  104. v-show="goodsDetail.data.storeInf.isFollowStore == true"
  105. />
  106. <img
  107. src="/goods/no_collection.png"
  108. alt=""
  109. v-show="goodsDetail.data.storeInf.isFollowStore == false"
  110. />
  111. <span>
  112. {{
  113. goodsDetail.data.storeInf.isFollowStore
  114. ? L["取消关注"]
  115. : L["关注店铺"]
  116. }}
  117. </span>
  118. </div>
  119. </div>
  120. </div>
  121. </div>
  122. <!-- 商品所属分类 ,联系客服,关注店铺 end-->
  123. <!-- 商品主要信息 start -->
  124. <div class="main_con">
  125. <!-- 商品相关 start -->
  126. <div class="goods_des">
  127. <!-- 商品图片列表 start -->
  128. <div class="goods_des_left">
  129. <!-- 商品放大镜效果 start-->
  130. <div class="goods_main_picture">
  131. <div class="mask" v-if="[5, 6].includes(goodsDetail.data.state)">
  132. <div class="circle">{{ L['已下架'] }}</div>
  133. </div>
  134. <div
  135. class="preview-box"
  136. @mousemove="move($event)"
  137. @mouseleave="out($event)"
  138. @mouseenter="enter($event)"
  139. ref="previewBox"
  140. >
  141. <div
  142. class="imageBorder"
  143. :class="{
  144. default_image: true,
  145. skeleton_default_image: firstLoading,
  146. }"
  147. :style="{ backgroundImage: 'url(' + defaultImage + ')' }"
  148. >
  149. <video
  150. v-if="
  151. currentDefaultImage == -1 && goodsDetail.data.goodsVideo
  152. "
  153. controls
  154. playsinline="playsinline"
  155. class="imageBorder default_image"
  156. :poster="defaultImage"
  157. autoplay
  158. ref="video"
  159. >
  160. <source
  161. :src="goodsDetail.data.goodsVideo"
  162. type="video/mp4"
  163. />
  164. </video>
  165. </div>
  166. <div
  167. class="v_btn"
  168. v-if="
  169. currentDefaultImage != -1 && goodsDetail.data.goodsVideo
  170. "
  171. >
  172. <img src="/goods/playV.png" alt="" @click="playV" />
  173. </div>
  174. <!-- 遮罩 start-->
  175. <div class="mask" ref="maskBox" v-show="maskShow"></div>
  176. <!-- 遮罩 end -->
  177. <!-- 底部放大镜icon图标 start -->
  178. <div
  179. class="magnifier_icon flex_row_center_center"
  180. v-show="!maskShow"
  181. >
  182. <i class="iconfont icon-sousuo"></i>
  183. </div>
  184. <!-- 底部放大镜icon图标 end -->
  185. </div>
  186. <!-- 右侧的放大后的图片 start -->
  187. <div
  188. class="goods_picture_big"
  189. style="border: 1px solid #eee"
  190. ref="zoomBox"
  191. v-show="maskShow"
  192. >
  193. <div
  194. class="default_image_big"
  195. :style="{ backgroundImage: 'url(' + defaultImage + ')' }"
  196. ref="pictureBig"
  197. ></div>
  198. </div>
  199. <!-- 右侧的放大后的图片 end -->
  200. </div>
  201. <!-- 商品放大镜效果 end -->
  202. <!-- 商品图片列表 start -->
  203. <div
  204. :class="{
  205. goods_picture_con: true,
  206. flex_row_between_center: true,
  207. skeleton_goods_picture_con: firstLoading,
  208. }"
  209. v-if="
  210. goodsDetail.data.defaultProduct &&
  211. goodsDetail.data.defaultProduct.goodsPics &&
  212. goodsDetail.data.defaultProduct.goodsPics.length > 0
  213. "
  214. >
  215. <i
  216. class="iconfont icon-ziyuan2 left_arrow"
  217. :class="{ no_left_arrow: currentDefaultImage == 0 }"
  218. @click="switchDefaultImage('left')"
  219. ></i>
  220. <div class="show_box">
  221. <ul
  222. class="goods_picture_list flex_row_start_center"
  223. ref="goodsPictureList"
  224. >
  225. <li
  226. v-for="(goodsImgItem, goodsImgIndex) in goodsDetail.data
  227. .defaultProduct.goodsPics"
  228. :key="goodsImgIndex"
  229. class="goods_img"
  230. :class="{
  231. goods_img_active: currentDefaultImage == goodsImgIndex,
  232. }"
  233. @click="selectDefaultImage(goodsImgItem, goodsImgIndex)"
  234. @mouseover="
  235. selectDefaultImage(goodsImgItem, goodsImgIndex)
  236. "
  237. >
  238. <div
  239. class="goods_image"
  240. :style="{
  241. backgroundImage: 'url(' + goodsImgItem + ')',
  242. }"
  243. ></div>
  244. </li>
  245. </ul>
  246. </div>
  247. <i
  248. class="iconfont icon-ziyuan11 right_arrow"
  249. :class="{
  250. no_left_arrow:
  251. currentDefaultImage ==
  252. goodsDetail.data.defaultProduct.goodsPics.length - 1,
  253. }"
  254. @click="switchDefaultImage('right')"
  255. ></i>
  256. </div>
  257. <!-- 商品图片列表 end -->
  258. <!-- 商品分享和收藏 start -->
  259. <div
  260. class="collection_share_btn flex_row_start_start"
  261. v-if="goodsDetail.data.state == 3"
  262. >
  263. <div
  264. class="collection_btn flex_row_start_center cursor_pointer"
  265. @click="collectGoods"
  266. >
  267. <img
  268. src="/goods/collection.png"
  269. alt=""
  270. v-if="goodsDetail.data.followGoods"
  271. />
  272. <img src="/goods/collection1.png" alt="" v-else />
  273. <span>{{
  274. goodsDetail.data.followGoods ? L["已收藏"] : L["收藏"]
  275. }}</span>
  276. </div>
  277. <div class="share_btn">
  278. <AddThis publicId="ra-5ab34ca22008ed41" />
  279. </div>
  280. </div>
  281. <!-- 商品分享和收藏 end -->
  282. </div>
  283. <!-- 商品图片列表 end -->
  284. <!-- 商品详细信息 start -->
  285. <div class="m_item_inner">
  286. <div class="item_info">
  287. <div
  288. :class="{
  289. detaile_name: true,
  290. skeleton_detaile_name: firstLoading,
  291. }"
  292. >
  293. {{ goodsDetail.data.goodsName }}
  294. </div>
  295. <div
  296. :class="{ p_ad: true, skeleton_p_ad: firstLoading }"
  297. v-if="goodsDetail.data.goodsBrief"
  298. >
  299. {{ goodsDetail.data.goodsBrief }}
  300. </div>
  301. <!-- 商品未下架即正常商品 start -->
  302. <div v-if="goodsDetail.data.state == 3 || firstLoading">
  303. <div class="summary">
  304. <div
  305. class="sld_summary_item summary_goods clearfix"
  306. v-if="goodsDetail.data.defaultProduct"
  307. >
  308. <div class="sld_summary_goods_left">
  309. <div class="goods_price flex_row_start_center">
  310. <!-- 在售价 -->
  311. <div>
  312. <span class="price_title">{{ L["价格"] }}</span>
  313. <strong
  314. :class="{
  315. p_price: true,
  316. skeleton_p_price: firstLoading,
  317. }"
  318. >
  319. <span>
  320. {{
  321. goodsDetail.data.goodsMoney == null
  322. ? "面议"
  323. : goodsDetail.data.goodsMoney
  324. }}
  325. </span>
  326. </strong>
  327. </div>
  328. </div>
  329. </div>
  330. </div>
  331. </div>
  332. <div v-if="
  333. goodsDetail.data.goodsMinOrder != undefined &&
  334. goodsDetail.data.goodsMinOrder
  335. " class="goodsMinOrder">
  336. <span class="">{{ L["最低采购数量:"] }}</span>
  337. <strong>
  338. <span>
  339. {{
  340. goodsDetail.data.goodsMinOrder == null
  341. ? "--"
  342. : goodsDetail.data.goodsMinOrder
  343. }}
  344. </span>
  345. </strong>
  346. </div>
  347. <!-- 商品简介 start -->
  348. <div class="summary-info">
  349. <div
  350. v-if="
  351. goodsDetail.data.goodsSummary != undefined &&
  352. goodsDetail.data.goodsSummary
  353. "
  354. class="summary_html"
  355. :style="goodsDetail.data.goodsSummaryBg"
  356. >
  357. <div
  358. class="summary_htmls"
  359. v-html="goodsDetail.data.goodsSummary"
  360. ></div>
  361. </div>
  362. </div>
  363. <!-- 规格 start -->
  364. <div
  365. class="goods_spec"
  366. v-if="
  367. goodsDetail.data.specs &&
  368. goodsDetail.data.specs.length > 0
  369. "
  370. >
  371. <div
  372. class="goods_spec_pre flex_row_start_start"
  373. v-for="(specItem, specIndex) in goodsDetail.data.specs"
  374. :key="specIndex"
  375. >
  376. <div
  377. class="goods_spec_pre_title"
  378. :title="specItem.specName"
  379. >
  380. {{ specItem.specName }}
  381. </div>
  382. <div class="goods_spec_pre_list flex_row_start_center">
  383. <!-- checkState : 1-选中,2-可选,3-禁用 -->
  384. <!-- 禁止选择 -->
  385. <div
  386. class="specval_pre cursor_pointer"
  387. :class="{
  388. specval_pre_disabled: sepcValItem.checkState == '3',
  389. }"
  390. v-for="(
  391. sepcValItem, specValIndex
  392. ) in specItem.specValueList"
  393. :key="specValIndex"
  394. v-show="sepcValItem.checkState == '3'"
  395. >
  396. <div
  397. class="specval_pre_image"
  398. :style="{
  399. backgroundImage: 'url(' + sepcValItem.image + ')',
  400. }"
  401. :title="sepcValItem.specValue"
  402. v-if="sepcValItem.image"
  403. ></div>
  404. <span class="specval_pre_text" v-else>{{
  405. sepcValItem.specValue
  406. }}</span>
  407. </div>
  408. <!-- 可选择 -->
  409. <div
  410. class="specval_pre cursor_pointer"
  411. :class="{
  412. specval_pre_active: sepcValItem.checkState == '1',
  413. }"
  414. v-for="(
  415. sepcValItem, specValIndex
  416. ) in specItem.specValueList"
  417. :key="specValIndex"
  418. @click="
  419. selectSpecVal(
  420. 'choice',
  421. specItem.specId,
  422. sepcValItem.specValueId
  423. )
  424. "
  425. v-show="sepcValItem.checkState != '3'"
  426. >
  427. <div
  428. class="goods_image"
  429. :style="{
  430. backgroundImage: 'url(' + goodsImgItem + ')',
  431. }"
  432. ></div>
  433. <div
  434. class="specval_pre_image"
  435. :style="{
  436. backgroundImage: 'url(' + sepcValItem.image + ')',
  437. }"
  438. :title="sepcValItem.specValue"
  439. v-if="sepcValItem.image"
  440. ></div>
  441. <span class="specval_pre_text" v-else>{{
  442. sepcValItem.specValue
  443. }}</span>
  444. <img
  445. src="/goods/check_mark.png"
  446. alt=""
  447. class="check_mark"
  448. v-if="sepcValItem.checkState == '1'"
  449. />
  450. </div>
  451. </div>
  452. </div>
  453. </div>
  454. <!-- 规格 end -->
  455. </div>
  456. <!-- 商品未下架即正常商品 end -->
  457. <template v-if="!firstLoading">
  458. <!-- 立即购买 加入购物车 收藏 ,分享 start-->
  459. <!-- 商品已下架 start -->
  460. <div
  461. class="options_btn"
  462. v-if="goodsDetail.data.state && goodsDetail.data.state != 3"
  463. >
  464. <p class="option_desc">{{L['商品已下架,欢迎挑选其他商品~']}}</p>
  465. <div class="goods_off_shelves">{{ L["商品已下架"] }}</div>
  466. <!-- 商品下架时的推荐商品 start -->
  467. <div class="recoOffShop">
  468. <div
  469. class="reCon"
  470. v-for="(
  471. {
  472. goodsImage,
  473. goodsName,
  474. goodsPrice,
  475. defaultProductId,
  476. },
  477. index
  478. ) in recomOffShop.data"
  479. :key="index"
  480. >
  481. <div class="reComImg">
  482. <router-link
  483. :to="'/goods/detail/'+ calcProductName(goodsName) +'_'+ defaultProductId"
  484. target="_blank"
  485. >
  486. <img :src="goodsImage" alt="" />
  487. </router-link>
  488. </div>
  489. <router-link
  490. class="recomName"
  491. :to="'/goods/detail/'+ calcProductName(goodsName) +'_'+ defaultProductId"
  492. target="_blank"
  493. >
  494. {{ goodsName }}
  495. </router-link>
  496. <span class="recomPrice">{{ goodsMoney }}</span>
  497. </div>
  498. </div>
  499. <!-- 商品下架时的推荐商品 end -->
  500. </div>
  501. <!-- 商品已下架 end -->
  502. <!-- 普通(活动)正常商品 start -->
  503. <div class="options_btn flex_row_start_center" v-else>
  504. <div
  505. class="goods_code"
  506. id="qrcodeAct"
  507. v-show="isShowQr"
  508. ></div>
  509. <div v-if="goodsDetail.data.state === 3" class="buy_now flex_row_center_center mt-20" @click="goBuy">
  510. {{ L["发送询盘"] }}
  511. </div>
  512. </div>
  513. <!-- 普通(活动)正常商品 end -->
  514. <!-- 立即购买 加入购物车 收藏 ,分享 end-->
  515. </template>
  516. </div>
  517. </div>
  518. <!-- 商品详细信息 end -->
  519. <!-- 相关推荐 start -->
  520. <div class="more_goods" v-if="goodsDetail.data.state == 3">
  521. <div class="more_goods_title">{{ L["看了又看"] }}</div>
  522. <div class="more_goods_list flex_column_center_center">
  523. <template v-if="firstLoading">
  524. <div
  525. class="more_goods_pre"
  526. v-for="(recommendItem, recommendIndex) in [
  527. { a: 1 },
  528. { b: 2 },
  529. { c: 3 },
  530. ]"
  531. :key="recommendIndex"
  532. >
  533. <router-link
  534. target="_blank"
  535. :to="'/goods/detail/'+ calcProductName(recommendItem.goodsName) +'_'+ recommendItem.defaultProductId"
  536. >
  537. <div
  538. :class="{
  539. more_goods_pre_img: true,
  540. skeleton_more_goods_pre_img: firstLoading,
  541. }"
  542. ></div>
  543. <p
  544. :class="{
  545. skeleton_more_goods_pre_goods_name: firstLoading,
  546. }"
  547. >
  548. {{ recommendItem.goodsName }}
  549. </p>
  550. <p>
  551. <span
  552. :class="{
  553. skeleton_more_goods_pre_goods_price: firstLoading,
  554. }"
  555. ></span>
  556. </p>
  557. </router-link>
  558. </div>
  559. </template>
  560. <template v-else>
  561. <div
  562. class="more_goods_pre"
  563. v-for="(
  564. recommendItem, recommendIndex
  565. ) in recommendeList.data"
  566. :key="recommendIndex"
  567. v-show="recommendIndex < 2"
  568. >
  569. <router-link
  570. target="_blank"
  571. :to="'/goods/detail/'+ calcProductName(recommendItem.goodsName) +'_'+ recommendItem.defaultProductId"
  572. >
  573. <div class="more_goods_pre_img flex_row_center_center">
  574. <img
  575. :src="recommendItem.goodsImage"
  576. :title="recommendItem.goodsName"
  577. />
  578. </div>
  579. <p>{{ recommendItem.goodsName }}</p>
  580. <p>{{ recommendItem.goodsMoney }}</p>
  581. </router-link>
  582. </div>
  583. </template>
  584. </div>
  585. </div>
  586. <!-- 相关推荐 end -->
  587. </div>
  588. <!-- 商品相关 end -->
  589. <!-- 店铺,及各种信息的切换 start -->
  590. <div
  591. class="container flex_row_start_start"
  592. ref="container"
  593. id="container"
  594. >
  595. <div class="left">
  596. <div class="store_info" v-if="goodsDetail.data.storeInf">
  597. <div class="store_info_title flex_row_start_center">
  598. <span
  599. class="store_type"
  600. v-if="goodsDetail.data.storeInf.isOwnStore == 1"
  601. >{{ L["自营"] }}</span
  602. >
  603. <router-link
  604. target="_blank"
  605. :to="'/store/'+ calcProductName(goodsDetail.data.storeInf.storeName) +'_'+ goodsDetail.data.storeInf.storeId"
  606. >
  607. <span class="store_title" @click="goStore()">{{
  608. goodsDetail.data.storeInf.storeName
  609. }}</span>
  610. </router-link>
  611. </div>
  612. <div class="store_des">
  613. <div class="store_des_pre pre_service">
  614. <span>{{ L["客服电话"] }}:</span>
  615. <span>{{ goodsDetail.data.storeInf.servicePhone }}</span>
  616. </div>
  617. <div class="store_des_pre pre_service">
  618. <span>{{ L["主营商品"] }}:</span>
  619. <span>{{ goodsDetail.data.storeInf.mainBusiness }}</span>
  620. </div>
  621. </div>
  622. <div class="store_btn flex_row_center_center">
  623. <div class="store_btn_pre flex_row_center_center">
  624. <img src="/goods/store.png" alt="" class="btn_pre_img" />
  625. <router-link
  626. target="_blank"
  627. :to="'/store/'+ calcProductName(goodsDetail.data.storeInf.storeName) +'_'+ goodsDetail.data.storeInf.storeId"
  628. class="go_store_btn"
  629. >
  630. {{ L["进入店铺"] }}
  631. </router-link>
  632. </div>
  633. <div
  634. class="store_btn_pre flex_row_center_center"
  635. @click="focusStore"
  636. >
  637. <img
  638. src="/goods/collection.png"
  639. alt=""
  640. v-if="goodsDetail.data.storeInf.isFollowStore"
  641. />
  642. <img src="/goods/no_collection.png" alt="" v-else />
  643. <span>{{
  644. goodsDetail.data.storeInf.isFollowStore
  645. ? L["取消关注"]
  646. : L["关注店铺"]
  647. }}</span>
  648. </div>
  649. </div>
  650. </div>
  651. <!-- 店铺推荐及热门收藏 end -->
  652. </div>
  653. <!-- 商品详情 评价 商品服务 店铺热销 start-->
  654. <div class="goods_description">
  655. <div class="goods_description_title flex_row_between_center">
  656. <div class="description_title_left flex_row_start_center">
  657. <span
  658. class="cursor_pointer"
  659. :class="{
  660. description_active: goodsDesctionType == 'detail',
  661. }"
  662. @click="goodsDescType('detail')"
  663. >{{ L["商品详情"] }}</span
  664. >
  665. <span
  666. class="cursor_pointer"
  667. v-if="
  668. goodsDetail.data.goodsAnnexList &&
  669. goodsDetail.data.goodsAnnexList.length > 0
  670. "
  671. :class="{
  672. description_active: goodsDesctionType == 'evaluate',
  673. }"
  674. @click="goodsDescType('evaluate')"
  675. >{{ L["商品说明"] }}</span
  676. >
  677. <span
  678. class="cursor_pointer"
  679. v-if="
  680. goodsDetail.data.serviceLabels &&
  681. goodsDetail.data.serviceLabels.length > 0
  682. "
  683. :class="{
  684. description_active: goodsDesctionType == 'service',
  685. }"
  686. @click="goodsDescType('service')"
  687. >{{ L["商品服务"] }}</span
  688. >
  689. <span
  690. class="cursor_pointer"
  691. :class="{
  692. description_active: goodsDesctionType == 'salestore',
  693. }"
  694. @click="goodsDescType('salestore')"
  695. >{{ L["店铺推荐"] }}</span
  696. >
  697. </div>
  698. </div>
  699. <div class="goods_description_con">
  700. <!-- 商品详情,规格参数,及详情富文本 start-->
  701. <div class="goods_des_con" v-if="goodsDesctionType == 'detail'">
  702. <div
  703. v-if="
  704. goodsDetail.data.brandName ||
  705. (goodsDetail.data.goodsParameterList &&
  706. goodsDetail.data.goodsParameterList.length > 0) ||
  707. goodsDetail.data.goodsDetails
  708. "
  709. >
  710. <div class="brand" v-if="goodsDetail.data.brandName">
  711. <span>{{ L["品牌"] }}: </span>
  712. <span>{{ goodsDetail.data.brandName }}</span>
  713. </div>
  714. <div
  715. v-if="
  716. goodsDetail.data.goodsParameterList &&
  717. goodsDetail.data.goodsParameterList.length > 0
  718. "
  719. >
  720. <div
  721. class="goods_parameter_list"
  722. :class="{ goods_paramter_more: !parameterShow }"
  723. >
  724. <div
  725. class="goods_parameter_pre"
  726. v-for="(parameterItem, paramterIndex) in goodsDetail
  727. .data.goodsParameterList"
  728. :key="paramterIndex"
  729. >
  730. <span>{{ parameterItem.parameterName }}: </span>
  731. <span>{{ parameterItem.parameterValue }}</span>
  732. </div>
  733. </div>
  734. <div
  735. class="collapse_unfold flex_row_center_center cursor_pointer"
  736. v-if="goodsDetail.data.goodsParameterList.length > 16"
  737. @click="openParameter"
  738. >
  739. <span>{{
  740. !parameterShow ? L["查看全部"] : L["收起全部"]
  741. }}</span>
  742. <i
  743. class="iconfont icon-ziyuan11-copy"
  744. v-if="!parameterShow"
  745. ></i>
  746. <i class="iconfont icon-ziyuan11-copy-copy" v-else></i>
  747. </div>
  748. </div>
  749. <div
  750. v-if="
  751. goodsDetail.data.topTemplateContent != undefined &&
  752. goodsDetail.data.topTemplateContent
  753. "
  754. class="goods_html"
  755. :style="goodsDetail.data.topTemplateContentBg"
  756. >
  757. <div
  758. class="goods_htmls"
  759. v-html="goodsDetail.data.topTemplateContent"
  760. ></div>
  761. </div>
  762. <div
  763. v-if="
  764. goodsDetail.data.goodsDetails != undefined &&
  765. goodsDetail.data.goodsDetails
  766. "
  767. class="goods_html"
  768. :style="goodsDetail.data.goodsDetailsBg"
  769. >
  770. <div
  771. class="goods_htmls"
  772. v-html="goodsDetail.data.goodsDetails"
  773. ></div>
  774. </div>
  775. <div
  776. v-if="
  777. goodsDetail.data.bottomTemplateContent != undefined &&
  778. goodsDetail.data.bottomTemplateContent
  779. "
  780. class="goods_html"
  781. :style="goodsDetail.data.bottomTemplateContentBg"
  782. >
  783. <div
  784. class="goods_htmls"
  785. v-html="goodsDetail.data.bottomTemplateContent"
  786. ></div>
  787. </div>
  788. </div>
  789. <div v-else>
  790. <SldCommonEmpty
  791. :tip="L['该商品暂无详情~']"
  792. totalWidth="934px"
  793. />
  794. </div>
  795. </div>
  796. <!-- 商品详情,规格参数,及详情富文本 end-->
  797. <!-- 商品评价 start -->
  798. <div
  799. class="goods_comments"
  800. v-if="goodsDesctionType == 'evaluate'"
  801. >
  802. <!--说明书下载begin-->
  803. <div
  804. class="download_warp"
  805. v-if="
  806. goodsDetail.data.goodsAnnexList &&
  807. goodsDetail.data.goodsAnnexList.length > 0
  808. "
  809. >
  810. <span style="margin: 10px; font-size: 14px"
  811. >{{ L["说明书下载"] }}:
  812. </span>
  813. <br />
  814. <p
  815. style="margin: 10px; font-size: 14px"
  816. v-for="(item, index) in goodsDetail.data.goodsAnnexList"
  817. :key="index"
  818. >
  819. <a
  820. style="text-decoration: underline"
  821. href="javascript:;"
  822. @click="downloadAdd(item)"
  823. >{{ item.annexName }}</a
  824. >
  825. </p>
  826. </div>
  827. <div v-else>
  828. {{ L["暂无说明"] }}
  829. </div>
  830. <!--说明书下载end-->
  831. </div>
  832. <!-- 商品评价 end -->
  833. <!-- 商品服务 start -->
  834. <div
  835. class="goods_server_list"
  836. v-if="goodsDesctionType == 'service'"
  837. >
  838. <div
  839. v-if="
  840. goodsDetail.data.serviceLabels &&
  841. goodsDetail.data.serviceLabels.length > 0
  842. "
  843. >
  844. <div
  845. class="goods_server_pre"
  846. v-for="(serverItem, serverIndex) in goodsDetail.data
  847. .serviceLabels"
  848. :key="serverIndex"
  849. >
  850. <div class="server_pre_top flex_row_start_center">
  851. <span class="server_pre_tips"></span>
  852. <span class="server_pre_name">{{
  853. serverItem.labelName
  854. }}</span>
  855. </div>
  856. <div class="server_pre_content">
  857. {{ serverItem.description }}
  858. </div>
  859. </div>
  860. </div>
  861. <div v-else>
  862. <SldCommonEmpty
  863. :tip="L['暂无商品服务~']"
  864. totalWidth="934px"
  865. />
  866. </div>
  867. </div>
  868. <!-- 商品服务 end -->
  869. <!-- 店铺推荐 start -->
  870. <div
  871. class="store_hot_sales"
  872. v-if="goodsDesctionType == 'salestore'"
  873. >
  874. <div
  875. v-if="
  876. recommendedList.data && recommendedList.data.length > 0
  877. "
  878. >
  879. <div class="store_hot_sales_list">
  880. <div
  881. class="goods_pre flex_column_between_start"
  882. v-for="(
  883. recommendItem, recommendIndex
  884. ) in recommendedList.data"
  885. :key="recommendIndex"
  886. >
  887. <router-link
  888. target="_blank"
  889. :to="'/goods/detail/'+ calcProductName(recommendItem.goodsName) +'_'+ recommendItem.defaultProductId"
  890. >
  891. <div class="flex_column_start_start">
  892. <div
  893. class="goods_pre_img"
  894. :style="{
  895. backgroundImage:
  896. 'url(' + recommendItem.goodsImage + ')',
  897. }"
  898. ></div>
  899. <div class="goods_name">
  900. {{ recommendItem.goodsName }}
  901. </div>
  902. </div>
  903. <div class="goods_price">
  904. <div class="selling_price">
  905. {{ recommendItem.goodsMoney }}
  906. </div>
  907. </div>
  908. </router-link>
  909. </div>
  910. </div>
  911. <div
  912. class="flex_row_end_center sld_pagination sld_page_bottom"
  913. v-if="
  914. recommendeData.data.pagination &&
  915. recommendeData.data.pagination.total
  916. "
  917. >
  918. <el-pagination
  919. @current-change="handleCurrentChangeSales"
  920. v-model:currentPage="salesCurrent"
  921. :page-size="salesPageSize"
  922. layout="prev, pager, next, jumper"
  923. :total="recommendeData.data.pagination.total"
  924. :hide-on-single-page="true"
  925. >
  926. </el-pagination>
  927. </div>
  928. </div>
  929. <div
  930. v-if="
  931. recommendedList.data && recommendedList.data.length == 0
  932. "
  933. class="flex_column_center_center empty_data"
  934. >
  935. <SldCommonEmpty
  936. :tip="L['暂无相关商品~']"
  937. totalWidth="934px"
  938. />
  939. </div>
  940. </div>
  941. <!-- 店铺推荐 end -->
  942. </div>
  943. </div>
  944. <!-- 商品详情 评价 商品服务 店铺热销 end-->
  945. </div>
  946. <!-- 店铺,及各种信息的切换 end -->
  947. </div>
  948. <!-- 商品主要信息 end -->
  949. </div>
  950. <EnquiryModal
  951. v-if="enquiryVis"
  952. :itemType="'GOODS'"
  953. :itemId="productId"
  954. @closeLoingModal="closeEnquiryModal"
  955. />
  956. </div>
  957. </div>
  958. </template>
  959. <script setup>
  960. import addrData from "@/assets/area.json";
  961. import { ElMessage, ElRate, ElDialog, ElPagination } from "element-plus";
  962. import { qrcanvas } from "qrcanvas";
  963. // import { lang_zn } from "@/assets/language/zh";
  964. import { getCurLanguage } from '@/composables/common.js';
  965. import { goodsInfo, useUserInfo } from "@/store/user.js";
  966. import { useFiltersStore } from "@/store/filter.js";
  967. const filtersStore = useFiltersStore();
  968. const goodsInfox = goodsInfo();
  969. const configInfo = useUserInfo();
  970. // const L = lang_zn;
  971. const L = getCurLanguage();
  972. const firstLoading = ref(true); //是否第一次加载
  973. const router = useRouter();
  974. const route = useRoute();
  975. const store = ref();
  976. const imgVisible = ref(false);
  977. const imgSource = ref("");
  978. const imgIndex = ref(-1);
  979. const vid = ref(0); //店铺id
  980. const proxy = getCurrentInstance();
  981. const goodsDetail = reactive({ data: {} }); //商品详情数据
  982. const isChoice = ref("default"); //是默认选中的,还是点击选择规格之后的 default:默认 choice:选择
  983. const productId = ref(""); //货品id
  984. const fullDisList = reactive({ data: [] }); //满优惠促销列表
  985. const couponList = reactive({ data: [] }); //获取店铺优惠券列表
  986. const pictureBig = ref(null); //大图的信息
  987. const maskBox = ref(null); //遮罩盒子的信息
  988. const maskShow = ref(false); //遮罩是否显示
  989. const previewBox = ref(null); // 左侧主图元素信息
  990. const zoomBox = ref(null); //左侧主图的父元素的信息
  991. const defaultImage = ref(""); //默认主图路径
  992. const currentDefaultImage = ref("0"); //默认主图显示第一张
  993. const currentSpecNum = ref(1); //商品编辑数量,默认数量为1
  994. const recommendeList = reactive({ data: [] }); //看了又看商品
  995. const storePopularList = reactive({ data: [] }); //店铺推荐及热门收藏
  996. const storePopularType = ref("collection"); //店铺推荐及热门收藏,默认显示店铺推荐
  997. const goodsDesctionType = ref("detail"); //商品详情,评价,商品服务,店铺热销,默认显示商品详情
  998. const goodsCommentsInfo = reactive({ data: {} }); //商品评价信息
  999. const evaluationType = ref(""); //商品评价类型,默认显示全部
  1000. const recommendedList = reactive({ data: [] }); //店铺推荐列表
  1001. const evaluationCurrent = ref(1); //评价列表默认第一页
  1002. const evaluationPageSize = ref(5); //评价列表默认一页显示5条数据
  1003. const salesCurrent = ref(1); //店铺热销列表默认一页
  1004. const salesPageSize = ref(20); //店铺热销列表默认一页显示20条数据
  1005. const recommendeData = reactive({ data: {} }); //店铺热销推荐数据
  1006. const couponModel = ref(false); //优惠券弹框是否显示
  1007. const cartListData = reactive({ data: goodsInfox.cartListData }); //获取vux的store中的购物车数据
  1008. const enquiryVis = ref(false); //登录弹框是否显示,默认不显示
  1009. const container = ref(null); // 商品详情页底部内容区
  1010. const containerTop = ref(0); //商品详情页底部内容区的top值
  1011. const containCon = ref(false); //固定内容区头部
  1012. const fullDiscountModel = ref(false); //满优惠弹框是否显示
  1013. const goodsPictureList = ref(null); //商品图片列表
  1014. const curCouponPage = ref(1); //当前为第一页优惠券
  1015. const couponPageSize = ref(6); //优惠券默认一页显示6条数据
  1016. const wxShareCode = ref(false); //微信分享二维码是否显示
  1017. const parameterShow = ref(false); //规格参数查看是否查看全部,默认为否
  1018. const score = ref(0); //好评率
  1019. const colors = ref(["#E2231A", "#E2231A", "#E2231A"]); //星星颜色
  1020. const curAddr = ref(-1);
  1021. const curAddrName = ref("");
  1022. const addrIdx = ref(0);
  1023. const otherAddrIdx = ref(0);
  1024. const othTopIdx = reactive({
  1025. 0: 0,
  1026. 1: 0,
  1027. 2: 0,
  1028. });
  1029. const otherTree = ref([
  1030. addrData[othTopIdx["0"]],
  1031. addrData[othTopIdx["0"]].children[othTopIdx["1"]],
  1032. ]);
  1033. const othAddrDe = ref(addrData);
  1034. const addrDialogVisible = ref(false);
  1035. const logFlag = ref(configInfo.loginFlag);
  1036. // 促销活动信息
  1037. const preSellInfo = reactive({ data: {} });
  1038. const pinInfo = reactive({ data: {} });
  1039. const seckillInfo = reactive({ data: {} });
  1040. const ladderInfo = reactive({ data: {} });
  1041. const address_list = reactive({ data: [] });
  1042. const isShowQr = ref(false);
  1043. const secInt = ref("");
  1044. const time = reactive({
  1045. day: "00",
  1046. hours: "00",
  1047. minutes: "00",
  1048. seconds: "00",
  1049. });
  1050. const judgeStock = computed(() => {
  1051. return (
  1052. goodsDetail.data.defaultProduct.productStock == 0 ||
  1053. (JSON.stringify(preSellInfo.data) != "{}" &&
  1054. preSellInfo.data.presellStock == 0) ||
  1055. (JSON.stringify(pinInfo.data) != "{}" && pinInfo.data.spellStock == 0) ||
  1056. (JSON.stringify(seckillInfo.data) != "{}" &&
  1057. seckillInfo.data.seckillStock == 0)
  1058. );
  1059. });
  1060. // 促销活动信息end
  1061. const scrollHandle = async (e) => {
  1062. if (process.client) {
  1063. let elementScrollTop = e.srcElement.scrollingElement.scrollTop; //获取页面滚动高度
  1064. if (
  1065. document.getElementById("container") &&
  1066. elementScrollTop > document.getElementById("container").offsetTop
  1067. ) {
  1068. containCon.value = true;
  1069. await proxy.$nextTick();
  1070. } else {
  1071. containCon.value = false;
  1072. }
  1073. }
  1074. };
  1075. // 点击播放视频
  1076. const playV = () => {
  1077. currentDefaultImage.value = -1;
  1078. defaultImage.value = "";
  1079. maskShow.value = false;
  1080. videoEnd();
  1081. };
  1082. //获取商品详情数据
  1083. const getInitDataStatic = async (proId) => {
  1084. let params = {
  1085. productId: proId,
  1086. };
  1087. const { data: value, pending: pending } = await useFetchRaw(
  1088. apiUrl + "v3/goods/front/goods/details",
  1089. { params: params }
  1090. );
  1091. const res = value._rawValue;
  1092. if (res.state == 200) {
  1093. let staticData = [
  1094. "brandId",
  1095. "brandName",
  1096. "categoryPath",
  1097. "categoryIdPath",
  1098. "goodsBrief",
  1099. "goodsDetails",
  1100. "goodsSummary",
  1101. "goodsBrief",
  1102. "goodsId",
  1103. "goodsName",
  1104. "goodsParameterList",
  1105. "goodsVideo",
  1106. "topTemplateContent",
  1107. "bottomTemplateContent",
  1108. "goodsAnnexList",
  1109. "serviceLabels",
  1110. "goodsMoney",
  1111. "goodsMinOrder",
  1112. "goodsParameterList",
  1113. ];
  1114. staticData.forEach((item) => {
  1115. if (item == "categoryPath") {
  1116. goodsDetail.data.categoryPath = res.data.categoryPath.split("->");
  1117. } else if (item == "categoryIdPath") {
  1118. goodsDetail.data.categoryIdPath = [
  1119. res.data.categoryId1,
  1120. res.data.categoryId2,
  1121. res.data.categoryId3,
  1122. ];
  1123. } else {
  1124. goodsDetail.data[item] = res.data[item];
  1125. }
  1126. });
  1127. if (goodsDetail.data.goodsSummary) {
  1128. goodsDetail.data.goodsSummary = quillEscapeToHtml(
  1129. goodsDetail.data.goodsSummary
  1130. );
  1131. //处理背景样式
  1132. if (
  1133. goodsDetail.data.goodsSummary.indexOf(
  1134. '<p style="display:none;" data-background="'
  1135. ) != -1
  1136. ) {
  1137. let bg = goodsDetail.data.goodsSummary
  1138. .split('<p style="display:none;" data-background="')[1]
  1139. .split('">')[0];
  1140. goodsDetail.data.goodsSummaryBg = bg;
  1141. }
  1142. }
  1143. if (goodsDetail.data.topTemplateContent) {
  1144. goodsDetail.data.topTemplateContent = quillEscapeToHtml(
  1145. goodsDetail.data.topTemplateContent
  1146. );
  1147. //处理背景样式
  1148. if (
  1149. goodsDetail.data.topTemplateContent.indexOf(
  1150. '<p style="display:none;" data-background="'
  1151. ) != -1
  1152. ) {
  1153. let bg = goodsDetail.data.topTemplateContent
  1154. .split('<p style="display:none;" data-background="')[1]
  1155. .split('">')[0];
  1156. goodsDetail.data.topTemplateContentBg = bg;
  1157. }
  1158. }
  1159. if (goodsDetail.data.goodsDetails) {
  1160. goodsDetail.data.goodsDetails = quillEscapeToHtml(
  1161. goodsDetail.data.goodsDetails
  1162. );
  1163. //处理背景样式
  1164. if (
  1165. goodsDetail.data.goodsDetails.indexOf(
  1166. '<p style="display:none;" data-background="'
  1167. ) != -1
  1168. ) {
  1169. let bg = goodsDetail.data.goodsDetails
  1170. .split('<p style="display:none;" data-background="')[1]
  1171. .split('">')[0];
  1172. goodsDetail.data.goodsDetailsBg = bg;
  1173. }
  1174. }
  1175. if (goodsDetail.data.bottomTemplateContent) {
  1176. goodsDetail.data.bottomTemplateContent = quillEscapeToHtml(
  1177. goodsDetail.data.bottomTemplateContent
  1178. );
  1179. //处理背景样式
  1180. if (
  1181. goodsDetail.data.bottomTemplateContent.indexOf(
  1182. '<p style="display:none;" data-background="'
  1183. ) != -1
  1184. ) {
  1185. let bg = goodsDetail.data.bottomTemplateContent
  1186. .split('<p style="display:none;" data-background="')[1]
  1187. .split('">')[0];
  1188. goodsDetail.data.bottomTemplateContentBg = bg;
  1189. }
  1190. }
  1191. currentDefaultImage.value = 0;
  1192. vid.value = res.data.storeInf.storeId;
  1193. // setTimeout(() => {
  1194. // sldStatEvent({ behaviorType: 'gpv', goodsId: goodsDetail.data.goodsId, storeId: vid.value });
  1195. // }, 3000)
  1196. } else {
  1197. ElMessage.error(res.msg);
  1198. }
  1199. if (!pending._rawValue) {
  1200. getInitDataDynamic(productId.value);
  1201. getRecommend();
  1202. getStorePopular();
  1203. getEvaluation();
  1204. addLog();
  1205. }
  1206. };
  1207. getInitDataStatic(calcProductId(route.path));
  1208. onMounted(() => {
  1209. setTimeout(() => {
  1210. sldStatEvent({ behaviorType: 'gpv', goodsId: goodsDetail.data.goodsId, storeId: vid.value,pageUrl: defaultUrl + router.currentRoute.value.path, referrerPageUrl: apiUrl });
  1211. }, 3000)
  1212. });
  1213. productId.value = calcProductId(route.path);
  1214. if (!logFlag.value) {
  1215. addrIdx.value = 1;
  1216. }
  1217. const getInitDataDynamic = async (proId, updateType) => {
  1218. let params = {
  1219. productId: proId,
  1220. };
  1221. const { data: value, pending: pending } = await useFetchRaw(
  1222. apiUrl + "v3/goods/front/goods/details2",
  1223. { params: params,
  1224. headers:{Authorization:'Bearer ' + filtersStore.getToken}
  1225. }
  1226. );
  1227. const res = value._rawValue;
  1228. if (res.state == 200) {
  1229. useHead({
  1230. title: res.data.seoInfo.seoTitle || 'Goods Detail',
  1231. meta: [
  1232. {
  1233. name: "description",
  1234. content: res.data.seoInfo.seoDesc,
  1235. },
  1236. {
  1237. name: "keywords",
  1238. content: res.data.seoInfo.seoKeywords,
  1239. },
  1240. ],
  1241. });
  1242. defaultImage.value = res.data.defaultProduct.goodsPics[0];
  1243. let dynamicData = [
  1244. "defaultProduct",
  1245. "deliverInfo",
  1246. "effectSpecValueIds",
  1247. "followGoods",
  1248. "specs",
  1249. "storeInf",
  1250. "sales",
  1251. "state",
  1252. "shareLink",
  1253. "shareImage",
  1254. "goodsMinOrder",
  1255. "isVirtualGoods",
  1256. ];
  1257. dynamicData.forEach((item) => {
  1258. goodsDetail.data[item] = res.data[item];
  1259. });
  1260. if (goodsDetail.data.state != 3) {
  1261. getRecom();
  1262. }
  1263. if (goodsDetail.data.defaultProduct.promotionType == 103) {
  1264. getPreSell(res.data.defaultProduct.productId);
  1265. } else if (goodsDetail.data.defaultProduct.promotionType == 102) {
  1266. getPin();
  1267. } else if (goodsDetail.data.defaultProduct.promotionType == 104) {
  1268. getSeckill();
  1269. } else if (goodsDetail.data.defaultProduct.promotionType == 105) {
  1270. getLadder();
  1271. } else {
  1272. preSellInfo.data = {};
  1273. pinInfo.data = {};
  1274. seckillInfo.data = {};
  1275. ladderInfo.data = {};
  1276. }
  1277. firstLoading.value = false;
  1278. }
  1279. };
  1280. //视频播放结束时触发
  1281. const videoEnd = () => {
  1282. nextTick(() => {
  1283. proxy.refs.video.onended = () => {
  1284. currentDefaultImage.value = 0;
  1285. defaultImage.value = goodsDetail.data.defaultProduct.goodsPics[0];
  1286. };
  1287. });
  1288. };
  1289. // 促销活动信息
  1290. const getPreSell = async (productId) => {
  1291. let param = {
  1292. productId: productId,
  1293. promotionId: goodsDetail.data.defaultProduct.promotionId,
  1294. };
  1295. const { data: value } = await useFetchRaw(
  1296. apiUrl + "v3/promotion/front/preSell/detail",
  1297. { params: param }
  1298. );
  1299. const res = value.value._rawValue;
  1300. if (res.state == 200) {
  1301. let now = new Date();
  1302. let preStartDate = new Date(res.data.startTime);
  1303. let preEndDate = new Date(res.data.endTime);
  1304. preSellInfo.data = res.data;
  1305. let countTime = 0;
  1306. preSellInfo.data.endTime = formatPreTime(new Date(res.data.endTime));
  1307. preSellInfo.data.startTime = formatPreTime(new Date(res.data.startTime));
  1308. if (now > preStartDate && now < preEndDate) {
  1309. preSellInfo.data.pre_run = 2; //活动进行中
  1310. countTime = res.data.distanceEndTime;
  1311. countDown(countTime);
  1312. } else if (now < preStartDate) {
  1313. preSellInfo.data.pre_run = 1; //活动未开始
  1314. countTime =
  1315. (new Date(res.data.startTime).getTime() - now.getTime()) / 1000;
  1316. countDown(countTime);
  1317. } else if (now > preEndDate) {
  1318. preSellInfo.data.pre_run = 3; //活动已结束
  1319. }
  1320. genQrcode();
  1321. } else {
  1322. ElMessage.error(res.msg);
  1323. }
  1324. };
  1325. const getPin = () => {
  1326. let param = {
  1327. productId: goodsDetail.data.defaultProduct.productId,
  1328. promotionId: goodsDetail.data.defaultProduct.promotionId,
  1329. };
  1330. get("v3/promotion/front/spell/detail", param).then((res) => {
  1331. if (res.state == 200) {
  1332. pinInfo.data = res.data;
  1333. let countTime = 0;
  1334. let now = new Date();
  1335. let startTime = new Date(res.data.startTime);
  1336. if (now < startTime) {
  1337. countTime = (startTime.getTime() - now.getTime()) / 1000;
  1338. countDown(countTime);
  1339. } else {
  1340. countTime = res.data.distanceEndTime;
  1341. countDown(countTime);
  1342. }
  1343. genQrcode();
  1344. } else {
  1345. ElMessage.error(res.msg);
  1346. }
  1347. });
  1348. };
  1349. const getSeckill = () => {
  1350. let param = {
  1351. productId: goodsDetail.data.defaultProduct.productId,
  1352. promotionId: goodsDetail.data.defaultProduct.promotionId,
  1353. };
  1354. get("v3/promotion/front/seckill/detail", param).then((res) => {
  1355. if (res.state == 200) {
  1356. seckillInfo.data = res.data;
  1357. let now = new Date();
  1358. let countTime = 0;
  1359. let startTime = new Date(res.data.startTime);
  1360. if (seckillInfo.data.state == 1 || seckillInfo.data.state == 2) {
  1361. countTime = res.data.distanceEndTime;
  1362. countDown(countTime);
  1363. } else {
  1364. countTime = startTime.getTime() - now.getTime();
  1365. }
  1366. genQrcode();
  1367. }
  1368. });
  1369. };
  1370. const countDown = (countTime) => {
  1371. secInt.value = setInterval(() => {
  1372. if (countTime == 0) {
  1373. getInitDataDynamic(calcProductId(route.path));
  1374. clearInterval(secInt.value);
  1375. } else {
  1376. countTime--;
  1377. let day = parseInt(countTime / 60 / 60 / 24);
  1378. let hours = parseInt((countTime / 60 / 60) % 24);
  1379. let minutes = parseInt((countTime / 60) % 60);
  1380. let seconds = parseInt(countTime % 60);
  1381. time.day = day;
  1382. time.hours = hours > 9 ? hours : "0" + hours;
  1383. time.minutes = minutes > 9 ? minutes : "0" + minutes;
  1384. time.seconds = seconds > 9 ? seconds : "0" + seconds;
  1385. }
  1386. }, 1000);
  1387. };
  1388. const formatPreTime = (time) => {
  1389. let op = new Date(time);
  1390. let year = op.getFullYear();
  1391. let month = op.getMonth() + 1;
  1392. let day = op.getDate();
  1393. let hour = op.getHours();
  1394. let minute = op.getMinutes();
  1395. let part1 = [year, month, day]
  1396. .map((i) => (i.toString().length < 2 ? `0${i}` : i))
  1397. .join("-");
  1398. let part2 = [hour, minute]
  1399. .map((i) => (i.toString().length < 2 ? `0${i}` : i))
  1400. .join(":");
  1401. return part1 + " " + part2;
  1402. };
  1403. const getLadder = () => {
  1404. let param = {
  1405. productId: goodsDetail.data.defaultProduct.productId,
  1406. promotionId: goodsDetail.data.defaultProduct.promotionId,
  1407. };
  1408. get("v3/promotion/front/ladder/group/detail", param).then((res) => {
  1409. if (res.state == 200) {
  1410. ladderInfo.data = res.data;
  1411. let now = new Date();
  1412. let countTime = 0;
  1413. let startTime = new Date(res.data.startTime);
  1414. if (now < startTime) {
  1415. countTime = (startTime.getTime() - now.getTime()) / 1000;
  1416. countDown(countTime);
  1417. ladderInfo.data.state = 1;
  1418. } else {
  1419. countTime = res.data.distanceEndTime;
  1420. countDown(countTime);
  1421. ladderInfo.data.state = 2;
  1422. }
  1423. genQrcode();
  1424. }
  1425. });
  1426. };
  1427. const genQrcode = () => {
  1428. if (judgeStock.value) {
  1429. return;
  1430. }
  1431. proxy.$nextTick(() => {
  1432. let canvas = qrcanvas({
  1433. data: goodsDetail.data.shareLink, //二维码内容
  1434. size: 125,
  1435. colorDark: "red",
  1436. });
  1437. setTimeout(() => {
  1438. document.getElementById("qrcodeAct").innerHTML = "";
  1439. document.getElementById("qrcodeAct").appendChild(canvas);
  1440. }, 10);
  1441. });
  1442. };
  1443. // 促销活动信息end
  1444. const recomOffShop = reactive({ data: [] });
  1445. const getRecom = () => {
  1446. get("v3/goods/front/goods/goodsList", {
  1447. storeId: goodsDetail.data.categoryId1,
  1448. }).then((res) => {
  1449. if (res.state == 200) {
  1450. let top = Math.floor(Math.random() * (res.data.list.length - 8)) + 8;
  1451. let end = top - 8;
  1452. recomOffShop.data = res.data.list
  1453. .filter(
  1454. (item) =>
  1455. item.defaultProductId != goodsDetail.data.defaultProduct.productId
  1456. )
  1457. .slice(end, top);
  1458. }
  1459. });
  1460. };
  1461. //添加足迹
  1462. const addLog = () => {
  1463. let params = {
  1464. productId: productId.value,
  1465. };
  1466. post("v3/member/front/productLookLog/add", params).then((res) => {});
  1467. };
  1468. //记录下载
  1469. const downloadAdd = (item) => {
  1470. let url = item.annexUrl;
  1471. let name = item.annexName;
  1472. if (filtersStore.getLoginFlag) {
  1473. post("v3/member/front/download/add", {
  1474. goodsId: item.goodsId,
  1475. annexId: item.annexId,
  1476. }).then((res) => {
  1477. });
  1478. }
  1479. const link = document.createElement("a");
  1480. fetch(url)
  1481. .then((res) => res.blob())
  1482. .then((blob) => {
  1483. link.href = URL.createObjectURL(blob);
  1484. link.download = name;
  1485. document.body.appendChild(link);
  1486. link.click();
  1487. window.URL.revokeObjectURL(link.href);
  1488. document.body.removeChild(link);
  1489. });
  1490. };
  1491. /**
  1492. * 选择规格值
  1493. * @param type:类型 值:choice,规格选择 default:默认
  1494. * @param specId:父级规格值
  1495. * @param specValueId:点击的当前的规格值
  1496. */
  1497. const selectSpecVal = (type, specId, specValueId) => {
  1498. isChoice.value = type == "choice" ? "choice" : "default";
  1499. let curParSpec = []; //当前点击的规格的父级id的当前项
  1500. curParSpec = goodsDetail.data.specs.filter((item) => item.specId == specId);
  1501. let curSPec = []; //当前点击的规格的规格id的当前项
  1502. curSPec = curParSpec[0].specValueList.filter(
  1503. (item1) => item1.specValueId == specValueId
  1504. );
  1505. curSPec[0].checkState = 1;
  1506. //被选择的规格值的id
  1507. let choiceSpecIds = [];
  1508. goodsDetail.data.specs.forEach((item) => {
  1509. if (item.specId != specId) {
  1510. item.specValueList.forEach((item1) => {
  1511. if (item1.checkState == "1") {
  1512. // checkState: 1-选中,2-可选,3-禁用
  1513. choiceSpecIds.push(item1.specValueId);
  1514. }
  1515. });
  1516. } else {
  1517. choiceSpecIds.push(specValueId);
  1518. }
  1519. });
  1520. let params = {
  1521. goodsId: goodsDetail.data.goodsId,
  1522. specValueIds: choiceSpecIds.join(","),
  1523. };
  1524. get("v3/goods/front/goods/productInfo", params).then((res) => {
  1525. if (res.state == 200) {
  1526. let result = res.data;
  1527. goodsDetail.data.defaultProduct = result.defaultProduct;
  1528. productId.value = result.defaultProduct.productId;
  1529. goodsDetail.data.specs = result.specs;
  1530. defaultImage.value = goodsDetail.data.defaultProduct.goodsPics[0];
  1531. currentDefaultImage.value = 0;
  1532. isShowQr.value = false;
  1533. // getInitDataDynamic(productId.value)
  1534. }
  1535. });
  1536. };
  1537. //改变数量按钮样式
  1538. const disStyle = reactive({
  1539. //目的是进入商品详情页面就让减按钮呈现禁止状态
  1540. color: "#DDDDDD",
  1541. backgroundColor: "#F8F8F8",
  1542. });
  1543. watch(
  1544. () => currentSpecNum.value,
  1545. () => {
  1546. //监听数量对加和减的样式做出调整
  1547. let productStock = goodsDetail.data.defaultProduct.productStock;
  1548. if (goodsDetail.data.defaultProduct.productStock == 0) {
  1549. productStock = 999;
  1550. }
  1551. if (currentSpecNum.value >= productStock) {
  1552. proxy.refs.add.style.color = "#DDDDDD";
  1553. proxy.refs.add.style.backgroundColor = "#F8F8F8";
  1554. } else if (currentSpecNum.value <= 1) {
  1555. disStyle.color = "#DDDDDD";
  1556. disStyle.backgroundColor = "#F8F8F8";
  1557. } else {
  1558. disStyle.color = "";
  1559. disStyle.backgroundColor = "";
  1560. proxy.refs.add.style.color = "";
  1561. proxy.refs.add.style.backgroundColor = "";
  1562. }
  1563. }
  1564. );
  1565. watch(currentSpecNum, () => {
  1566. if (currentSpecNum.value > goodsDetail.data.defaultProduct.productStock) {
  1567. currentSpecNum.value = goodsDetail.data.defaultProduct.productStock;
  1568. }
  1569. let reg = /\./g;
  1570. let reg0 = /0+\d/;
  1571. if (
  1572. currentSpecNum.value &&
  1573. (reg.test(currentSpecNum.value) || currentSpecNum.value <= 0)
  1574. ) {
  1575. currentSpecNum.value = 1;
  1576. }
  1577. });
  1578. //发送询盘
  1579. const goBuy = () => {
  1580. enquiryVis.value = true;
  1581. };
  1582. //关闭登录弹框
  1583. const closeEnquiryModal = () => {
  1584. enquiryVis.value = false;
  1585. };
  1586. //获取看了又看商品(人气数)
  1587. const getRecommend = async () => {
  1588. let params = {
  1589. storeId: vid.value,
  1590. sort: 5,
  1591. pageSize: 3,
  1592. current: 1,
  1593. };
  1594. const { data: value } = await useFetchRaw(
  1595. apiUrl + "v3/goods/front/goods/goodsList",
  1596. { params: params, key: params.sort.toString() }
  1597. );
  1598. const res = value._rawValue;
  1599. if (res.state == 200) {
  1600. let result = res.data;
  1601. recommendeList.data = result.list;
  1602. recommendeList.data.map(
  1603. (item) => (item.goodsPrice = new Number(item.goodsPrice).toFixed(2))
  1604. );
  1605. } else {
  1606. ElMessage.error(res.msg);
  1607. }
  1608. };
  1609. //获取店铺推荐的商品(销量数);获取热门收藏的商品(收藏数) type:recommend销量数 collection:收藏数
  1610. const getStorePopular = async (type) => {
  1611. if (type == "" || !type) {
  1612. type = "recommend";
  1613. } else {
  1614. storePopularType.value = type;
  1615. }
  1616. let params = {
  1617. storeId: vid.value,
  1618. sort:
  1619. storePopularType.value == "recommend"
  1620. ? 1
  1621. : storePopularType.value == "collection"
  1622. ? 6
  1623. : "",
  1624. pageSize: 6,
  1625. current: 1,
  1626. };
  1627. const { data: value } = await useFetchRaw(
  1628. apiUrl + "v3/goods/front/goods/goodsList",
  1629. { params: params, key: params.sort.toString() }
  1630. );
  1631. const res = value._rawValue;
  1632. if (res.state == 200) {
  1633. let result = res.data;
  1634. storePopularList.data = result.list;
  1635. storePopularList.data.map(
  1636. (item) => (item.goodsPrice = new Number(item.goodsPrice).toFixed(2))
  1637. );
  1638. } else {
  1639. ElMessage.error(res.msg);
  1640. }
  1641. };
  1642. //切换商品详情,评价,服务,热销
  1643. const goodsDescType = (type) => {
  1644. // let elementScrollTop =
  1645. // window.pageYOffset ||
  1646. // document.documentElement.scrollTop ||
  1647. // document.body.scrollTop; //获取页面滚动高度
  1648. // if (elementScrollTop > containerTop.value + 33 + 48) {
  1649. // window.scrollTo({
  1650. // top: containerTop.value,
  1651. // behavior: "smooth",
  1652. // });
  1653. // }
  1654. if (type == "" || !type) {
  1655. goodsDesctionType.value = "detail";
  1656. } else {
  1657. goodsDesctionType.value = type;
  1658. }
  1659. if (type == "salestore") {
  1660. getSalestore();
  1661. }
  1662. };
  1663. //获取商品评价
  1664. const getEvaluation = (type) => {
  1665. if (!type) {
  1666. evaluationType.value = "";
  1667. } else {
  1668. evaluationType.value = type;
  1669. }
  1670. let params = {
  1671. productId: productId.value,
  1672. current: evaluationCurrent.value,
  1673. pageSize: evaluationPageSize.value,
  1674. type: evaluationType.value,
  1675. };
  1676. get("v3/goods/front/goods/comment", params).then((res) => {
  1677. if (res.state == 200) {
  1678. let result = res.data;
  1679. goodsCommentsInfo.data = result;
  1680. score.value = Number(goodsCommentsInfo.data.avgScore);
  1681. goodsCommentsInfo.data &&
  1682. goodsCommentsInfo.data.list &&
  1683. goodsCommentsInfo.data.list.length > 0 &&
  1684. goodsCommentsInfo.data.list.map((commentsItem) => {
  1685. commentsItem.memberName =
  1686. commentsItem.memberName.slice(0, 1) +
  1687. "***" +
  1688. commentsItem.memberName.slice(
  1689. commentsItem.memberName.length - 1,
  1690. commentsItem.memberName.length
  1691. );
  1692. });
  1693. } else {
  1694. ElMessage.error(res.msg);
  1695. }
  1696. });
  1697. };
  1698. //评价列表上一页
  1699. const handlePrevCilickChange = () => {
  1700. if (evaluationCurrent.value == 1) {
  1701. evaluationCurrent.value = 1;
  1702. } else {
  1703. evaluationCurrent.value--;
  1704. }
  1705. getEvaluation(evaluationType.value);
  1706. };
  1707. //评价列表下一页
  1708. const handleNextCilickChange = () => {
  1709. if (evaluationCurrent.value >= goodsCommentsInfo.data.list.length) {
  1710. evaluationCurrent.value = goodsCommentsInfo.data.list.length;
  1711. } else {
  1712. evaluationCurrent.value++;
  1713. }
  1714. getEvaluation(evaluationType.value);
  1715. };
  1716. //评价列表当前选择页
  1717. const handleCurrentChange = (val) => {
  1718. evaluationCurrent.value = val;
  1719. getEvaluation(evaluationType.value);
  1720. };
  1721. //获取热销店铺推荐
  1722. const getSalestore = () => {
  1723. let params = {
  1724. storeId: goodsDetail.data.storeInf.storeId,
  1725. sort: 7,
  1726. pageSize: salesPageSize.value,
  1727. current: salesCurrent.value,
  1728. };
  1729. get("v3/goods/front/goods/goodsList", params).then((res) => {
  1730. if (res.state == 200) {
  1731. let result = res.data;
  1732. recommendeData.data = result;
  1733. recommendedList.data = result.list;
  1734. recommendedList.data.map(
  1735. (item) => (item.goodsPrice = new Number(item.goodsPrice).toFixed(2))
  1736. );
  1737. } else {
  1738. ElMessage.error(res.msg);
  1739. }
  1740. });
  1741. };
  1742. //店铺热销列表上一页
  1743. const handlePrevCilickChangeSales = () => {
  1744. if (evaluationCurrent.value == 1) {
  1745. salesCurrent.value = 1;
  1746. } else {
  1747. salesCurrent.value--;
  1748. }
  1749. getSalestore();
  1750. };
  1751. //店铺热销列表下一页
  1752. const handleNextCilickChangeSales = () => {
  1753. if (salesCurrent.value >= goodsCommentsInfo.data.list.length) {
  1754. salesCurrent.value = goodsCommentsInfo.data.list.length;
  1755. } else {
  1756. salesCurrent.value++;
  1757. }
  1758. getSalestore();
  1759. };
  1760. //店铺热销列表当前选择页
  1761. const handleCurrentChangeSales = (val) => {
  1762. salesCurrent.value = val;
  1763. getSalestore();
  1764. };
  1765. //关注店铺及取消关注
  1766. const headercat = ref(null)
  1767. const focusStore = () => {
  1768. if (filtersStore.getLoginFlag) {
  1769. //已登录
  1770. let params = {
  1771. storeIds: goodsDetail.data.storeInf.storeId,
  1772. isCollect: !goodsDetail.data.storeInf.isFollowStore,
  1773. };
  1774. post("v3/member/front/followStore/edit", params).then((res) => {
  1775. if (res.state == 200) {
  1776. goodsDetail.data.storeInf.isFollowStore =!goodsDetail.data.storeInf.isFollowStore;
  1777. proxy.refs.headercat.setfollowStore(goodsDetail.data.storeInf.isFollowStore? "true" : "false")
  1778. if (goodsDetail.data.storeInf.isFollowStore) {
  1779. sldStatEvent({
  1780. behaviorType: "fol",
  1781. storeId: goodsDetail.data.storeInf.storeId,
  1782. });
  1783. }
  1784. }
  1785. });
  1786. } else {
  1787. //未登录提示登录
  1788. return openLoginDialog({
  1789. onRegister: () => {
  1790. router.push({
  1791. path: "/register",
  1792. });
  1793. },
  1794. onForgot: () => {
  1795. router.push({
  1796. path: "/member/login/forget",
  1797. });
  1798. },
  1799. });
  1800. }
  1801. };
  1802. //商品收藏及取消收藏
  1803. const collectGoods = () => {
  1804. if (filtersStore.getLoginFlag) {
  1805. //已登录
  1806. let params = {
  1807. productIds: productId.value,
  1808. isCollect: !goodsDetail.data.followGoods,
  1809. };
  1810. post("v3/member/front/followProduct/edit", params).then((res) => {
  1811. if (res.state == 200) {
  1812. goodsDetail.data.followGoods = !goodsDetail.data.followGoods;
  1813. if (goodsDetail.data.followGoods) {
  1814. sldStatEvent({
  1815. behaviorType: "fav",
  1816. goodsId: goodsDetail.data.goodsId,
  1817. storeId: goodsDetail.data.storeInf.storeId,
  1818. });
  1819. }
  1820. } else {
  1821. ElMessage.error(res.msg);
  1822. }
  1823. });
  1824. } else {
  1825. //未登录提示登录
  1826. return openLoginDialog({
  1827. onRegister: () => {
  1828. router.push({
  1829. path: "/register",
  1830. });
  1831. },
  1832. onForgot: () => {
  1833. router.push({
  1834. path: "/member/login/forget",
  1835. });
  1836. },
  1837. });
  1838. }
  1839. };
  1840. //点击查看全部查看全部的商品规格参数
  1841. const openParameter = () => {
  1842. parameterShow.value = !parameterShow.value;
  1843. };
  1844. //选择商品主图
  1845. const selectDefaultImage = (goodsImgItem, goodsImgIndex) => {
  1846. defaultImage.value = goodsImgItem;
  1847. currentDefaultImage.value = goodsImgIndex;
  1848. };
  1849. //切换商品主图
  1850. const switchDefaultImage = (type) => {
  1851. let defaultImagelength = goodsDetail.data.defaultProduct.goodsPics.length;
  1852. if (type == "left") {
  1853. currentDefaultImage.value--;
  1854. if (currentDefaultImage.value <= 0) {
  1855. currentDefaultImage.value = 0;
  1856. }
  1857. defaultImage.value =
  1858. goodsDetail.data.defaultProduct.goodsPics[currentDefaultImage.value];
  1859. } else {
  1860. currentDefaultImage.value++;
  1861. if (currentDefaultImage.value >= defaultImagelength) {
  1862. currentDefaultImage.value = defaultImagelength - 1;
  1863. }
  1864. defaultImage.value =
  1865. goodsDetail.data.defaultProduct.goodsPics[currentDefaultImage.value];
  1866. }
  1867. goodsPictureListsLeft();
  1868. };
  1869. //图片列表的left移动的距离
  1870. const goodsPictureListsLeft = () => {
  1871. //获取 goods_picture_list 的元素
  1872. let goodsPictureLists = goodsPictureList.value;
  1873. //列表默认显示5张图片
  1874. if (
  1875. goodsDetail.data.defaultProduct.goodsPics.length > 5 &&
  1876. currentDefaultImage.value >= 0
  1877. ) {
  1878. /* 分析找规律:
  1879. 如果有8张图片,点击右键 最大可以向左移动的距离为 8 - 5既3张图,left移动的距离为 3 * - 66px; currentDefaultImage.value从0开始的
  1880. 其中:66px为每一个元素需要每次移动的距离 为图片的宽度与图片之间的间距的和
  1881. currentDefaultImage.value == 4;goodsPictureLists.style.left = 0;
  1882. currentDefaultImage.value == 5;goodsPictureLists.style.left = (5-4) (1) * -66px;
  1883. currentDefaultImage.value == 6;goodsPictureLists.style.left = (5-2) (2) * -66px;
  1884. currentDefaultImage.value == 7;goodsPictureLists.style.left = (5-3) (2) * -66px;
  1885. 点击左键,最大可以向右移动的距离为 8 - 5 即3张
  1886. currentDefaultImage.value == 0;goodsPictureLists.style.left = 0;
  1887. currentDefaultImage.value == 1; goodsPictureLists.style.left = -66px;
  1888. currentDefaultImage.value == 2; goodsPictureLists.style.left = 2 * -66px;
  1889. currentDefaultImage.value == 3; goodsPictureLists.style.left = 3 * -66px;
  1890. */
  1891. if (currentDefaultImage.value > 4) {
  1892. goodsPictureLists.style.left =
  1893. (currentDefaultImage.value - 4) * -66 + "px";
  1894. }
  1895. if (
  1896. currentDefaultImage.value <
  1897. goodsDetail.data.defaultProduct.goodsPics.length - 4
  1898. ) {
  1899. goodsPictureLists.style.left = currentDefaultImage.value * -66 + "px";
  1900. }
  1901. }
  1902. };
  1903. //获取元素距离父元素的顶部及左边的距离
  1904. const offset = (el) => {
  1905. if (process.client) {
  1906. let top = el.offsetTop;
  1907. let left = el.offsetLeft;
  1908. if (el.offsetParent) {
  1909. el = el.offsetParent;
  1910. top += el.offsetTop;
  1911. left += el.offsetLeft;
  1912. }
  1913. return {
  1914. left: left,
  1915. top: top,
  1916. };
  1917. }
  1918. };
  1919. if (process.client) {
  1920. nextTick(() => {
  1921. containerTop.value = offset(container.value).top;
  1922. window.addEventListener("scroll", scrollHandle); //绑定页面滚动事件
  1923. window.addEventListener("click", () => {
  1924. addrDialogVisible.value = false;
  1925. });
  1926. });
  1927. }
  1928. //鼠标移动
  1929. const move = (e) => {
  1930. if (currentDefaultImage.value == -1) {
  1931. return;
  1932. }
  1933. //主图父元素的信息 宽,高
  1934. let previewsBox = previewBox.value;
  1935. let previewBoxWidth = previewsBox.offsetWidth;
  1936. let previewBoxHeight = previewsBox.offsetHeight;
  1937. //主图父元素距离顶部的距离
  1938. let previewsBoxLeft = offset(previewsBox).left;
  1939. let previewsBoxTop = offset(previewsBox).top;
  1940. // 遮罩盒子的信息宽,高
  1941. let masksBox = maskBox.value;
  1942. let maskBoxWidth = masksBox.offsetWidth;
  1943. let maskBoxHeight = masksBox.offsetHeight;
  1944. //鼠标距离屏幕距离
  1945. let moveX = e.clientX;
  1946. let moveY = e.clientY;
  1947. //获取左侧大图父元素的信息
  1948. let zoomsBox = zoomBox.value;
  1949. let zoomBoxWidth = zoomsBox.offsetWidth;
  1950. let zoomBoxHeight = zoomsBox.offsetHeight;
  1951. // 获取大图元素的信息宽,高
  1952. let pictureBigBox = pictureBig.value;
  1953. let pictureBigWidth = pictureBigBox.offsetWidth;
  1954. let pictureBigHeight = pictureBigBox.offsetHeight;
  1955. //获取滚动条的高度
  1956. let scroll = document.documentElement.scrollTop || document.body.scrollTop;
  1957. //主图距离父元素的left及top值
  1958. let left = moveX - previewsBoxLeft - maskBoxWidth / 2;
  1959. let top;
  1960. if (scroll > 0) {
  1961. top = moveY - previewsBoxTop + scroll - maskBoxHeight / 2;
  1962. } else {
  1963. top = moveY - previewsBoxTop - maskBoxHeight / 2;
  1964. }
  1965. //移动限制最大宽度,及最大高度
  1966. let maxWidth = previewBoxWidth - maskBoxWidth;
  1967. let maxHeight = previewBoxHeight - maskBoxHeight;
  1968. left = left < 0 ? 0 : left > maxWidth ? maxWidth : left;
  1969. top = top < 0 ? 0 : top > maxHeight ? maxHeight : top;
  1970. //比列
  1971. let parcentX = left / maxWidth;
  1972. let parcentY = top / maxHeight;
  1973. //遮罩层的定位值
  1974. maskBox.value.style.left = left + "px";
  1975. maskBox.value.style.top = top + "px";
  1976. //大图元素的定位值
  1977. pictureBig.value.style.left =
  1978. parcentX * (zoomBoxWidth - pictureBigWidth) + "px";
  1979. pictureBig.value.style.top =
  1980. parcentY * (zoomBoxHeight - pictureBigHeight) + "px";
  1981. pictureBig.value.style.width =
  1982. (previewBoxWidth / maskBoxWidth) * zoomBoxWidth + "px";
  1983. pictureBig.value.style.height =
  1984. (previewBoxHeight / maskBoxHeight) * zoomBoxHeight + "px";
  1985. };
  1986. //鼠标移出
  1987. const out = () => {
  1988. maskShow.value = false;
  1989. };
  1990. //鼠标移入
  1991. const enter = () => {
  1992. if (currentDefaultImage.value == -1) {
  1993. return;
  1994. }
  1995. addrDialogVisible.value = false;
  1996. maskShow.value = true;
  1997. };
  1998. // 评论区查看图片
  1999. const showImg = (index, img) => {
  2000. imgSource.value = img;
  2001. imgIndex.value = index;
  2002. imgVisible.value = true;
  2003. };
  2004. //分享
  2005. const share = (type) => {
  2006. let title = goodsDetail.data.goodsName; //需要分享的标题,这里取商品名字
  2007. let url = goodsDetail.data.shareLink; //分享的地址,用户点击可以进入到该商品
  2008. let content = goodsDetail.data.goodsBrief; //自定义内容,这里取商品广告词
  2009. let targetUrl = ""; //跳转的url地址
  2010. if (type == "weixin") {
  2011. wxShareCode.value = true;
  2012. //微信
  2013. let canvas = qrcanvas({
  2014. data: url, //二维码内容
  2015. size: 100,
  2016. colorDark: "red",
  2017. });
  2018. let share_wx_qrcode = document.getElementById("share_wx_qrcode");
  2019. if (
  2020. wxShareCode.value &&
  2021. share_wx_qrcode != null &&
  2022. share_wx_qrcode != undefined
  2023. ) {
  2024. document.getElementById("share_wx_qrcode").innerHTML = "";
  2025. document.getElementById("share_wx_qrcode").appendChild(canvas);
  2026. }
  2027. } else if (type == "qzone") {
  2028. wxShareCode.value = false;
  2029. //QQ空间
  2030. targetUrl =
  2031. "https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?title=" +
  2032. encodeURIComponent(title) +
  2033. "&desc=" +
  2034. encodeURIComponent(content) +
  2035. "&summary=" +
  2036. encodeURIComponent(content) +
  2037. "&url=" +
  2038. encodeURIComponent(url);
  2039. window.open(targetUrl, "_blank");
  2040. } else if (type == "sina") {
  2041. wxShareCode.value = false;
  2042. //新浪微博
  2043. targetUrl =
  2044. "https://service.weibo.com/share/share.php?title=" +
  2045. encodeURIComponent(content + "「" + title + "」" + " 点这里" + url);
  2046. window.open(targetUrl, "_blank");
  2047. }
  2048. };
  2049. const refreshInfo = () => {
  2050. history.go(0);
  2051. };
  2052. const updateFllow = (e) => {
  2053. goodsDetail.data.storeInf.isFollowStore = e.state == "true" ? true : false;
  2054. };
  2055. //暴露的变量及方法
  2056. </script>
  2057. <style lang="scss">
  2058. @import "@/assets/style/base.scss";
  2059. @import "@/assets/style/theme.scss";
  2060. @import "@/assets/style/iconfont.css";
  2061. @import "@/assets/style/goodsDetail.scss";
  2062. .popular_list_empty {
  2063. height: 95px;
  2064. font-size: 14px;
  2065. /*font-family: Microsoft YaHei;*/
  2066. font-weight: 400;
  2067. color: #666666;
  2068. }
  2069. .imageBorder {
  2070. border: 1px solid #eee;
  2071. }
  2072. .goods_picture_big {
  2073. border: 1px solid #eee;
  2074. }
  2075. .el-radio__inner:hover {
  2076. border-color: $colorMain;
  2077. }
  2078. .el-radio__input.is-checked .el-radio__inner {
  2079. border-color: $colorMain;
  2080. background: $colorMain;
  2081. }
  2082. .el-radio__input.is-checked + .el-radio__label {
  2083. color: $colorMain;
  2084. }
  2085. .el-radio {
  2086. margin-bottom: 10px;
  2087. display: flex;
  2088. align-items: flex-start;
  2089. white-space: unset;
  2090. margin-right: unset;
  2091. }
  2092. .el-radio-button__inner,
  2093. .el-radio-group {
  2094. /* display: block; */
  2095. line-height: 1;
  2096. vertical-align: middle;
  2097. }
  2098. .el-radio__label {
  2099. font-size: 13px;
  2100. width: 320px;
  2101. overflow: hidden;
  2102. text-overflow: ellipsis;
  2103. display: -webkit-box;
  2104. -webkit-line-clamp: 2;
  2105. -webkit-box-orient: vertical;
  2106. word-break: break-all;
  2107. line-height: 22px;
  2108. margin-top: -5px;
  2109. }
  2110. .evaluationes {
  2111. color: #3b4 !important;
  2112. }
  2113. .sld_goods_detail .goods_htmls .ql-video {
  2114. width: 525px;
  2115. height: 315px;
  2116. }
  2117. .sld_goods_detail .goods_htmls a {
  2118. display: inline-block;
  2119. margin: 5px auto;
  2120. color: #0000ff;
  2121. text-decoration: underline;
  2122. }
  2123. .sld_goods_detail .goods_htmls table {
  2124. border-collapse: collapse;
  2125. padding: 0;
  2126. }
  2127. .sld_goods_detail .goods_htmls td,
  2128. .sld_goods_detail .goods_htmls th {
  2129. border: 1px solid #ddd;
  2130. padding: 5px 10px;
  2131. }
  2132. .sld_goods_detail .goods_htmls ol li,
  2133. .sld_goods_detail .goods_htmls ul li {
  2134. list-style: unset;
  2135. }
  2136. .sld_goods_detail {
  2137. .summary {
  2138. .coupon {
  2139. .el-dialog__header {
  2140. padding-top: 18px;
  2141. padding-bottom: 18px;
  2142. .el-dialog__title {
  2143. color: #333333;
  2144. /*font-family: Microsoft YaHei;*/
  2145. font-weight: bold;
  2146. }
  2147. .el-dialog__close {
  2148. color: #333333;
  2149. font-size: 20px;
  2150. }
  2151. }
  2152. .el-dialog__body {
  2153. background: #f8f8f8;
  2154. }
  2155. }
  2156. }
  2157. }
  2158. .summary-info{
  2159. }
  2160. .mt-20{
  2161. margin-top: 20px;
  2162. }
  2163. .summary_htmls img{
  2164. width: 100%;
  2165. }
  2166. </style>